/*
 * client_app.h
 */

#include <exception>
#include <iostream>
#include <string>
#include <vector>
#include "app_listener.h"
#include "client_app.h"
#include "engine.h"
#include "campaign_info.h"
#include "game_info.h"
#include "../../common/src/debug.h"
#include "../../common/src/lock.h"
#include "../../common/src/message.h"
#include "../../common/src/message_queue.h"
#include "../../common/src/protocol.h"
#include "../../common/src/session.h"
#include "../../common/src/synchro_list.h"
#include "../../common/src/socket.h"
#include "../../common/src/socket_exception.h"
#include "../../common/src/util.h"

const std::string ClientApp::CMD_EXIT = "q";
const std::string ClientApp::CMD_LIST_CAMPAIGNS = "lc";
const std::string ClientApp::CMD_LIST_GAMES = "lg";
const std::string ClientApp::CMD_ABORT_GAME = "ag";
const std::string ClientApp::CMD_CREATE_GAME = "cg";
const std::string ClientApp::CMD_JOIN_GAME = "jg";
const std::string ClientApp::CMD_START_GAME = "sg";

ClientApp::ClientApp() : q(),
                         games(),
                         campaigns(),
                         listeners(),
                         engine(this),
                         t_engine(engine) {
    try {
        running = true;
        connected = false;
        //connect(ipaddr, port);
        t_engine.start();
    } catch (SocketException& e) {
        PRINT("[!] connect() triggered an exception.");
        if (sock != NULL) delete sock;
        throw;
    }
}

ClientApp::~ClientApp() {
    t_engine.join();
    if (connected) {
        delete session;
        while (!q.empty()) delete q.pop();
        Lock games_lock(games.getMutex());
        std::vector<GameInfo*>* gv = games.getVector();
        for (int i = 0; i < gv->size(); ++i) delete (*gv)[i];
        Lock campaigns_lock(campaigns.getMutex());
        std::vector<CampaignInfo*>* cv = campaigns.getVector();
        for (int i = 0; i < cv->size(); ++i) delete (*cv)[i];
    }
}

void ClientApp::connect(std::string username, std::string& ipaddr, int port) {
    try {
        sock = new Socket(port, ipaddr.c_str());
    } catch (SocketException& e) {
        PRINT("[!] connect() triggered an exception.");
        if (sock != NULL) delete sock;
        throw;
    }
    sock->connect();
    this->username = username;
    std::string sessionID;
    Protocol proto;
    Message* msg = proto.recvMessage(*sock); 
    sessionID = msg->getFrom();
    loadCampaigns(msg);
    delete msg;
    this->session = new Session(sessionID, sock, q);
    connected = true;
}

void ClientApp::exit() {
    running = false;
}

void ClientApp::exec() {
    std::string line;
    Message* msg;
    running = true;
    while (running) {
        getline(std::cin, line);
        if (line.size() == 0) continue;
        if (line == CMD_EXIT) {
            running = false;
            continue;
        }

        if (!connected) continue;

        if (!session->isAlive()) {
            PRINT("[!] Server disconnected.");
            running = false;
            continue;
        }
        std::vector<std::string>* tokens = Util::split(line, " ");
        if ((*tokens)[0] == CMD_LIST_GAMES) {
            requestGamesList(); 
        } else if ((*tokens)[0] == CMD_LIST_CAMPAIGNS) {
            //listCampaigns();
        } else if ((*tokens)[0] == CMD_CREATE_GAME) {
            requestCreateGame((*tokens)[1], (*tokens)[2]);
        } else if ((*tokens)[0] == CMD_JOIN_GAME) {
            requestJoinGame((*tokens)[1]);
        } else if ((*tokens)[0] == CMD_START_GAME) {
            requestStartGame();
        } else if ((*tokens)[0] == CMD_ABORT_GAME) {
            requestAbortGame();
        } else {
            try {
                msg = new Message(session->getID(), Protocol::MSG_TEXT);
                msg->addRecord(line);
                session->sendMessage(msg);
            } catch (SocketException& e) {
                PRINT("[!] Session.sendData() triggered an exception.");
            }
        }
        delete tokens;
    }
}

void ClientApp::requestGamesList() {
    Message* msg = new Message(session->getID(), Protocol::MSG_LIST_GAMES);
    session->sendMessage(msg);
}

/*
void ClientApp::listCampaigns() {
    Lock lock(campaigns.getMutex());
    std::vector<CampaignInfo*>* v = campaigns.getVector();
    for (int i = 0; i < v->size(); ++i) {
        std::cout << "CampaignID=" + (*v)[i]->getCampaignID()
                  << " CampaignName=" + (*v)[i]->getName()
                  << " Difficulty=" + (*v)[i]->getDifficulty()
                  << " MinPlayers=" + (*v)[i]->getMinPlayers()
                  << " Levels_size=" + (*v)[i]->getLevelsSize()
                  << " imageID=" + (*v)[i]->getImageID()
                  << std::endl;
    }
}
*/

void ClientApp::requestAbortGame() {
    Message* msg = new Message(session->getID(), Protocol::MSG_GAME_ABORT);
    session->sendMessage(msg);
}

void ClientApp::requestCreateGame(std::string name, std::string campaignID) {
    Message* msg = new Message(session->getID(), Protocol::MSG_CREATE_GAME);
    std::string units[3];
    units[0] = username;
    units[1] = name;
    units[2] = campaignID;
    msg->addRecord(units, 3);
    session->sendMessage(msg);
}

void ClientApp::requestJoinGame(std::string gameID) {
    Message* msg = new Message(session->getID(), Protocol::MSG_JOIN_GAME);
    std::string units[2];
    units[0] = username;
    units[1] = gameID;
    msg->addRecord(units, 2);
    session->sendMessage(msg);
}

void ClientApp::requestStartGame() {
    Message* msg = new Message(session->getID(), Protocol::MSG_START_GAME);
    session->sendMessage(msg);
}

void ClientApp::loadCampaigns(Message* msg) {
    Lock lock(campaigns.getMutex());
    std::vector<CampaignInfo*>* v = campaigns.getVector();
    std::vector<std::string>* records = msg->getRecords();
    if (records == NULL || records->size() == 0) {
        delete records;
        return;
    }
    std::vector<std::string>* units = NULL; 
    for (int i = 0; i < records->size(); ++i) {
        units = msg->getUnits((*records)[i]);
        if (units == NULL) continue;
        v->push_back(new CampaignInfo((*units)[0],
                                      (*units)[1],
                                      (*units)[2],
                                      (*units)[3],
                                      (*units)[4],
                                      (*units)[5]));
        delete units;
    }
    delete records;
}

bool ClientApp::isRunning() {
    return running;
}

bool ClientApp::isConnected() {
    return connected;
}

SynchroVector<GameInfo*>& ClientApp::getGames() {
    return games;
}

SynchroVector<CampaignInfo*>& ClientApp::getCampaigns() {
    return campaigns;
}

SynchroList<AppListener*>& ClientApp::getListeners() {
    return listeners;
}

MessageQueue& ClientApp::getMessageQueue() {
    return q;
}

void ClientApp::addListener(AppListener* listener) {
    Lock lock(listeners.getMutex());
    std::list<AppListener*>* l = listeners.getList();
    for (std::list<AppListener*>::iterator it=l->begin(); it!=l->end(); it++) {
        if (*it == listener) return; //avoid adding duplicates
    }
    l->push_back(listener);
}

void ClientApp::removeListener(AppListener* listener) {
    Lock lock(listeners.getMutex());
    std::list<AppListener*>* l = listeners.getList();
    l->remove(listener);
}

//{+}
